use std::sync::LazyLock;
use bittle::{Bits, BitsMut};
#[allow(unused_imports)]
use crate::imports::js;

include!("./table.rs");

const ZOBRIST_LEN:usize = BOARD_LEN * 3;
static ZOBRIST:LazyLock<[u64; ZOBRIST_LEN]> = LazyLock::new(||{
    let mut a = [0u64; ZOBRIST_LEN];
    for i in 0..ZOBRIST_LEN {
        a[i] = js::random_range_u(0, usize::MAX) as u64;
    }
    a
});


#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub struct Board {
    bits_piece: u128,
    bits_color: u128,
    hash: u64,
}
impl Default for Board {
    fn default() -> Self {
        Self {
            bits_piece: 0,
            bits_color: 0,
            hash: 0,
        }
    }
}
impl Board {
    #[inline] pub fn load(&mut self, from:&Board) -> &mut Self {
        self.bits_piece = from.bits_piece;
        self.bits_color = from.bits_color;
        self.hash = from.hash;
        self
    }

    #[inline] pub fn hash(&self) -> u64 {
        self.hash
    }

    #[inline] pub fn get(&self, loc:Loc) -> Piece {
        let bit_piece = self.bits_piece.test_bit_le(loc) as u32;
        let bit_color = self.bits_color.test_bit_le(loc) as u32;
        unsafe { std::mem::transmute(bit_piece + (bit_piece & bit_color)) }
    }

    #[inline] pub fn set_by_old(&mut self, loc:Loc, p:Piece, old:Piece) -> &mut Self {
        self.hash ^= ZOBRIST[old as usize * BOARD_LEN];
        match p {
            Piece::Empty => {
                self.bits_piece.clear_bit_le(loc);
            }
            Piece::Black => {
                self.bits_piece.set_bit_le(loc);
                self.bits_color.clear_bit_le(loc);
            }
            Piece::White => {
                self.bits_piece.set_bit_le(loc);
                self.bits_color.set_bit_le(loc);
            }
        }
        self.hash ^= ZOBRIST[p as usize * BOARD_LEN];
        self
    }
    #[inline] pub fn set(&mut self, loc:Loc, p:Piece) -> &mut Self {
        self.set_by_old(loc, p, self.get(loc))
    }

    #[inline] pub fn n_black(&self) -> usize {
        let mask = (self.bits_piece ^ self.bits_color) & self.bits_piece;
        mask.count_ones() as usize
    }

    #[inline] pub fn n_white(&self) -> usize {
        let mask = self.bits_piece & self.bits_color;
        mask.count_ones() as usize
    }

    #[inline] fn can_move_way(&self, lt:&[u8;8], i_lt:usize, side:u32, n:usize) -> usize {
        let loc = lt[i_lt] as Loc;
        let p = self.get(loc) as u32;
        if ((LOC_WAY_MASK[loc as usize][i_lt] as u32 * side) & (p & side)) == 0 {
            return (p & (3-side)) as usize * n
        }
        return self.can_move_way(&LOC_WAY_TABLE[loc as usize], i_lt, side, n+1)
    }
    #[inline] pub fn can_move(&self, loc:Loc, pcur:Piece) -> bool {
        if self.get(loc) != Piece::Empty {return false}
        let side = pcur.side() as u32;
        let center = &LOC_WAY_TABLE[loc as usize];
        let mut n_all = 0;
        for i_lt in 0..center.len() {
            n_all += self.can_move_way(center, i_lt, side, 0);
        }
        return n_all > 0
    }

    pub fn make_ps_all(&self) -> [[u32;BOARD_SIZE];54] {
        let mut ps_all:[[u32;BOARD_SIZE];54] = [[0u32;BOARD_SIZE]; 54];
        for i in 0..ps_all.len() {
            let locs = LINE_LOCS[i];
            let ps = &mut ps_all[i];
            ps[0] = self.get(locs[0] as Loc) as u32;
            for i in 1..BOARD_SIZE {
                let p = self.get(locs[i] as u32) as u32;
                if locs[i] == locs[0] {break}
                let is_end = (locs[i] == locs[0]) as i32;
                ps[i] = p & ((is_end - 1) as u32);
            }
        }
        ps_all
    }

    fn moves_line(&self, line_locs:&[u8;BOARD_SIZE], pcur:Piece, bits:&mut u128) {
        let mut ps_n = BOARD_SIZE;
        let ps = {
            let mut ps:[u8;BOARD_SIZE] = [0u8;BOARD_SIZE];
            ps[0] = self.get(line_locs[0] as u32) as u8;
            for i in 1..BOARD_SIZE {
                let p = self.get(line_locs[i] as u32) as u32;
                if line_locs[i] == line_locs[0] {
                    ps_n = i; break;
                }
                let is_end = (line_locs[i] == line_locs[0]) as i32;
                ps[i] = (p & ((is_end - 1) as u32)) as u8;
            }
            ps
        };
        let state_i = {
            let mut state_i = 0;
            let mut base = 1;
            for i in 0..BOARD_SIZE {
                state_i += (ps[i] as u32) * base;
                base *= 3;
            }
            state_i as usize
        };
        let move_bits = LINE_MOVE_BITS[state_i];
        let n_shift = (pcur as usize - 1) * 10;
        for i in 0..ps_n {
            let loc = line_locs[i];
            let mask = 1 << (i + n_shift);
            let mask_hit = ((move_bits & mask) == mask) as u32;
            let join = mask_hit;
            *bits |= (BOARD_HEAD_BIT >> (loc as usize)) * join as u128;
        }
    }
    #[inline] pub fn moves(&self, pcur:Piece) -> Vec<Loc> {
        let mut bits:u128 = 0;
        for i in 0..LINE_LOCS.len() {
            self.moves_line(&LINE_LOCS[i], pcur, &mut bits);
        }
        Self::loc_bits_to_vec(bits)
    }
    pub fn loc_bits_to_vec(bits:u128) -> Vec<Loc> {
        let mut locs = Vec::with_capacity(MOVES_FIXED_SIZE);
        for i in 0..BOARD_LEN as u32 {
            if bits.test_bit_le(i) {
                locs.push(i as Loc);
            }
        }
        locs
    }

    #[inline] pub fn moves_nums(&self, pcur:Piece) -> usize {
        (0..BOARD_LEN).reduce(|n,i|{n + if self.can_move(i as Loc, pcur) {1} else {0}}).unwrap_or(0)
    }

    #[inline] fn swap_way(&mut self, lt:&[u8;8], i_lt:usize, side:u32) -> bool {
        let i = lt[i_lt] as u32;
        let p = self.get(i) as u32;
        if ((LOC_WAY_MASK[i as usize][i_lt] as u32 * side) & (p & side)) == 0 {
            let ptb = p & PIECE_SIDE_MAP[side as usize] as u32;
            PIECE_TO_BOOL[ptb as usize]
        } else if self.swap_way(&LOC_WAY_TABLE[i as usize], i_lt, side) {
            self.set(i,  PIECE_SIDE_MAP[side as usize]);
            true
        } else {
            false
        }
    }

    #[inline] fn swap(&mut self, loc:Loc, pcur:Piece) {
        let side = pcur.side() as u32;
        let center = LOC_WAY_TABLE[loc as usize];
        self.swap_way(&center, 0, side);
        self.swap_way(&center, 1, side);
        self.swap_way(&center, 2, side);
        self.swap_way(&center, 3, side);
        self.swap_way(&center, 4, side);
        self.swap_way(&center, 5, side);
        self.swap_way(&center, 6, side);
        self.swap_way(&center, 7, side);
    }

    #[inline] pub fn down(&mut self, loc:Loc, pcur:Piece) {
        debug_assert!(loc_in_board(loc));
        self.set(loc, pcur);
        self.swap(loc, pcur);
    }

    pub fn to_display(&self) -> String {
        let mut lines:Vec<String> = Vec::new();
        for y in 0..BOARD_SIZE {
            let mut line:[u8;BOARD_SIZE] = [0u8;BOARD_SIZE];
            for x in 0..BOARD_SIZE {
                let loc = (y * BOARD_SIZE) + x;
                line[x] = self.get(loc as u32) as u8;
            }
            lines.push(format!("[{}] {:?}", y, line));
        }
        lines.join("\n")
    }

    pub fn to_text(&self, pcur:Piece) -> String {
        let mut text:String = String::with_capacity(BOARD_LEN+1);
        for loc in 0..BOARD_LEN as u32 {
            let pstr = format!("{}", self.get(loc) as u8);
            text.push_str(&pstr);
        }
        text.push_str(&format!("{}", pcur as u8));
        text
    }
}
